// 
// Copyright (C) 2000-2003
// magustek co.
//

#ifndef MAGUS_THREAD_HPP
#define MAGUS_THREAD_HPP

#include <magus/config.hpp>
#include <magus/utility.hpp>
#include <magus/thread/mutex.hpp>
#include <magus/thread/detail/config.hpp>
#include <magus/thread/xtime.hpp>
#include <magus/thread/condition.hpp>
#include <cassert>
#include <algorithm>	//std::find
#include <list>
#include <memory>

#if defined(MAGUS_HAS_WINTHREADS)
#   include <windows.h>
#   include <process.h>
#elif defined(MAGUS_HAS_PTHREADS)
#   include <signal.h>
#   include <pthread.h>
#endif

#include <cstdio>	// for debug

namespace magus {

class thread_functor
{
public:
	virtual void operator()(void) = 0;
};

} // namespace magus

namespace {

class thread_param
{
public:
    thread_param(magus::thread_functor& threadfunc)
        : m_threadfunc(threadfunc), m_started(false)
    {
    }
    void wait()
    {
        magus::mutex::scoped_lock scoped_lock(m_mutex);
        while (!m_started)
            m_condition.wait(scoped_lock);
    }
    void started()
    {
        magus::mutex::scoped_lock scoped_lock(m_mutex);
        m_started = true;
        m_condition.notify_one();
    }

    magus::mutex m_mutex;
    magus::condition m_condition;
    magus::thread_functor& m_threadfunc;
    bool m_started;
};

extern "C" {
#if defined(MAGUS_HAS_WINTHREADS)
    inline unsigned __stdcall thread_proxy(void* param)
#elif defined(MAGUS_HAS_PTHREADS)
    inline static void* thread_proxy(void* param)
#endif
    {
        try
        {
            thread_param* p = static_cast<thread_param*>(param);
            magus::thread_functor& threadfunc = p->m_threadfunc;
            p->started();
            threadfunc();
        }
        catch (...)
        {
        }
        return 0;
    }
}

} // unnamed namespace


namespace magus {

struct xtime;

class MAGUS_THREAD_DECL thread : private noncopyable
{
public:
	thread()
	    : m_joinable(false)
	{
	#if defined(MAGUS_HAS_WINTHREADS)
	    m_thread = reinterpret_cast<void*>(GetCurrentThread());
	    m_id = GetCurrentThreadId();
	#elif defined(MAGUS_HAS_PTHREADS)
	    m_thread = pthread_self();
	#endif
	}
	
	thread(thread_functor& threadfunc)
	    : m_joinable(true)
	{
	    thread_param param(threadfunc);
	#if defined(MAGUS_HAS_WINTHREADS)
	    m_thread = reinterpret_cast<void*>(_beginthreadex(0, 0, &thread_proxy, &param, 0, &m_id));
	    if (!m_thread)
	        throw thread_resource_error();
	#elif defined(MAGUS_HAS_PTHREADS)
	    int res = 0;
	    res = pthread_create(&m_thread, 0, &thread_proxy, &param);
	    if (res != 0)
	        throw thread_resource_error();
	#endif
	    param.wait();
	}
	
	~thread()
	{
	    if (m_joinable)
	    {
	#if defined(MAGUS_HAS_WINTHREADS)
	        int res = 0;
	        res = CloseHandle(reinterpret_cast<HANDLE>(m_thread));
	        assert(res);
	#elif defined(MAGUS_HAS_PTHREADS)
	        pthread_detach(m_thread);
	#endif
	    }
	}
	
	bool operator==(const thread& other) const
	{
	#if defined(MAGUS_HAS_WINTHREADS)
	    return other.m_id == m_id;
	#elif defined(MAGUS_HAS_PTHREADS)
	    return pthread_equal(m_thread, other.m_thread) != 0;
	#endif
	}
	
	bool operator!=(const thread& other) const
	{
	    return !operator==(other);
	}
	
	void join()
	{
	    int res = 0;
	#if defined(MAGUS_HAS_WINTHREADS)
	    res = WaitForSingleObject(reinterpret_cast<HANDLE>(m_thread), INFINITE);
	    assert(res == WAIT_OBJECT_0);
	    res = CloseHandle(reinterpret_cast<HANDLE>(m_thread));
	    assert(res);
	#elif defined(MAGUS_HAS_PTHREADS)
	    res = pthread_join(m_thread, 0);
	    assert(res == 0);
	#endif
	    // This isn't a race condition since any race that could occur would
	    // have us in undefined behavior territory any way.
	    m_joinable = false;
	}
	
	static void sleep(const xtime& xt)
	{
	    for (int foo=0; foo < 5; ++foo)
	    {
	#if defined(MAGUS_HAS_WINTHREADS)
	        int milliseconds;
	        to_duration(xt, milliseconds);
	        Sleep(milliseconds);
	#elif defined(MAGUS_HAS_PTHREADS)
	#   if defined(MAGUS_HAS_PTHREAD_DELAY_NP)
	        timespec ts;
	        to_timespec_duration(xt, ts);
	        int res = 0;
	        res = pthread_delay_np(&ts);
	        assert(res == 0);
	#   elif defined(MAGUS_HAS_NANOSLEEP)
	        timespec ts;
	        to_timespec_duration(xt, ts);
	
	        //  nanosleep takes a timespec that is an offset, not
	        //  an absolute time.
	        nanosleep(&ts, 0);
	#   else
	        mutex mx;
	        mutex::scoped_lock lock(mx);
	        condition cond;
	        cond.timed_wait(lock, xt);
	#   endif
	#endif
	        xtime cur;
	        xtime_get(&cur, TIME_UTC);
	        if (xtime_cmp(xt, cur) <= 0)
	            return;
	    }
	}
	
	static void yield()
	{
	#if defined(MAGUS_HAS_WINTHREADS)
	    Sleep(0);
	#elif defined(MAGUS_HAS_PTHREADS)
	#   if defined(MAGUS_HAS_SCHED_YIELD)
	    int res = 0;
	    res = sched_yield();
	    assert(res == 0);
	#   elif defined(MAGUS_HAS_PTHREAD_YIELD)
	    int res = 0;
	    res = pthread_yield();
	    assert(res == 0);
	#   else
	    xtime xt;
	    xtime_get(&xt, TIME_UTC);
	    sleep(xt);
	#   endif
	#endif
	}
	
	void interrupt(int sig)
	{
	#if defined(MAGUS_HAS_PTHREADS)
		pthread_kill(m_thread, sig);
	#endif
	}

private:
#if defined(MAGUS_HAS_WINTHREADS)
    void* m_thread;
    unsigned int m_id;
#elif defined(MAGUS_HAS_PTHREADS)
    pthread_t m_thread;
#endif
    bool m_joinable;
};

class MAGUS_THREAD_DECL thread_group : private noncopyable
{
public:
	thread_group()
	{
	}
	
	~thread_group()
	{
	    // We shouldn't have to scoped_lock here, since referencing this object
	    // from another thread while we're deleting it in the current thread is
	    // going to lead to undefined behavior any way.
	    for (std::list<thread*>::iterator it = m_threads.begin();
	         it != m_threads.end(); ++it)
	    {
	        delete (*it);
	    }
	}
	
	int thread_size()
	{
		return m_threads.size();
	}
	
	
	thread* create_thread(thread_functor& threadfunc)
	{
	    // No scoped_lock required here since the only "shared data" that's
	    // modified here occurs inside add_thread which does scoped_lock.
	    std::auto_ptr<thread> thrd(new thread(threadfunc));
	    add_thread(thrd.get());
	    return thrd.release();
	}
	
	void add_thread(thread* thrd)
	{
	    mutex::scoped_lock scoped_lock(m_mutex);
	
	    // For now we'll simply ignore requests to add a thread object multiple
	    // times. Should we consider this an error and either throw or return an
	    // error value?
	    std::list<thread*>::iterator it = std::find(m_threads.begin(), m_threads.end(), thrd);
	    assert(it == m_threads.end());
	    if (it == m_threads.end()) 
	        m_threads.push_back(thrd);
		
	}
	
	void remove_thread(thread* thrd)
	{
	    mutex::scoped_lock scoped_lock(m_mutex);
	
	    // For now we'll simply ignore requests to remove a thread object that's
	    // not in the group. Should we consider this an error and either throw or
	    // return an error value?
	    std::list<thread*>::iterator it = std::find(m_threads.begin(), m_threads.end(), thrd);
	    assert(it != m_threads.end());
	    if (it != m_threads.end())
	        m_threads.erase(it);
	}
	
	void join_all()
	{
	    mutex::scoped_lock scoped_lock(m_mutex);
	    for (std::list<thread*>::iterator it = m_threads.begin();
	         it != m_threads.end(); ++it)
	    {
	        (*it)->join();
	        delete (*it);
	    }
		m_threads.clear();
	}
	
private:
    std::list<thread*> m_threads;
    mutex m_mutex;
};

} // namespace magus

// Change Log:
//    16 JUN 03  Initial version.

#endif // MAGUS_THREAD_HPP
